import math
import random
import pyglet
from pyglet.gl import *
import debris
import data
import particle
from vec2d import Vec2d

class Hook:
    def __init__(self, image):
            """Hook Class"""
            self.name = "Hook"
            #Correct the collision so it corrseponds with the eye of the hook
            self.collision_offset = Vec2d(0,-24)
            self.collision_radius = 8
            hook_img = data.image(image)
            # anchor the hook image at the eyelet, which is the rotation point
            hook_img.anchor_x = hook_img.width / 2
            hook_img.anchor_y = hook_img.height
            self.hook_sprite = pyglet.sprite.Sprite(hook_img)
            self.hook_offset = Vec2d(hook_img.anchor_x, 0)
            self.hook_speed = 0

    @property
    def collision_position(self):
        """Our collision position is really the hook position"""
        return Vec2d(self.hook_sprite.x + self.collision_offset.x,
                self.hook_sprite.y + self.collision_offset.y) 

    def draw(self):
        glLineWidth(1)
        glPointSize(5)
        glColor3f(*self.COLOR)
        glBegin(GL_LINE_STRIP)
        #glBegin(GL_POINTS)
        for position, velocity, last in self.segments:
            glVertex2f(*position)
        if self.state == self.TROLLING_STATE:
            glVertex2f(self.hook_sprite.x, self.hook_sprite.y)
        glEnd()

        self.hook_sprite.draw()

class Dynamite(Hook):
    def __init__(self, image):
        """Dynamite Class"""
        #print "Do nothing"
        """Hook Class"""
        self.name = "Dynamite"
        #Correct the collision so it corrseponds with the eye of the hook
        self.collision_offset = Vec2d(0,-24)
        self.collision_radius = 108
        hook_img = data.image(image)
        # anchor the hook image at the eyelet, which is the rotation point
        hook_img.anchor_x = hook_img.width / 2
        hook_img.anchor_y = hook_img.height
        self.hook_sprite = pyglet.sprite.Sprite(hook_img)
        self.hook_offset = Vec2d(hook_img.anchor_x, 0)
        self.hook_speed = 0
        
    def draw(self):
        #print "debug"
        glLineWidth(1)
        glPointSize(5)
        glColor3f(*self.COLOR)
        glBegin(GL_LINE_STRIP)
        #glBegin(GL_POINTS)
        for position, velocity, last in self.segments:
            glVertex2f(*position)
        if self.state == self.TROLLING_STATE:
            glVertex2f(self.hook_sprite.x, self.hook_sprite.y)
        glEnd()  
        self.hook_sprite.draw()
        #self.draw_circle( self.self.collision_offset.x,  self.self.collision_offset.y, self.collision_radius)

class FishingPole:

    LENGTH = 0.12
    SEGMENTS = 10
    RIGIDITY = 50.0
    START_ANGLE = 40
    MAX_ANGLE = 150
    MIN_ANGLE = 30
    START_X = 0.2
    START_Y = 0.8
    MASS = 1.0
    COLOR = (0.3, 0.2, 0)
    
    def __init__(self, game):
        self.start_pt = Vec2d(self.START_X * game.width, self.START_Y * game.height)
        self.set_angle(self.START_ANGLE)
        # compute approx end point
        self.end_pt = self.start_pt + self.direction * self.LENGTH * game.width
        self.line_force = Vec2d(0, -5)
        self.line = FishingLine(game, self.end_pt)
        self.game = game

    def set_angle(self, angle):
        """Set the angle of the rod in degrees"""
        angle = max(min(angle, self.MAX_ANGLE), self.MIN_ANGLE)
        radians = math.radians(angle)
        self.direction = Vec2d(math.cos(radians), math.sin(radians))

    def rotate(self, angle):
        """Add the angle to the current rod angle"""
        self.direction.rotate(angle)
        new_angle = self.direction.get_angle()
        if new_angle < self.MIN_ANGLE or new_angle > self.MAX_ANGLE:
            self.set_angle(new_angle)

    def start_cast(self):
        self.line.start_cast()
        data.sound('start_cast.wav').play()
    
    def reset_line(self):
        self.line.reset()

    def reel_in(self):
        self.line.reel_in()

    def crank(self):
        """Crank the line in a little"""
        self.line.crank()
        data.sound('start_cast.wav').play()

    def is_line_dangling(self):
        return self.line.state == self.line.DANGLING_STATE

    def update(self, dt):
        man = self.game.background.man_front_sprite
        self.start_pt = Vec2d(man.x + man.width * 0.69, man.y + man.height * 0.54)
        self.line.update(dt)
    
    def draw(self, game):
        segment_len = (self.LENGTH * game.width) / self.SEGMENTS
        direction = Vec2d(*self.direction)
        glLineWidth(2)
        glColor3f(*self.COLOR)
        glBegin(GL_LINE_STRIP)
        point = Vec2d(*self.start_pt)
        rigidity = self.RIGIDITY
        rigidity_loss = 1.0 - 1.0 / self.SEGMENTS
        normal_force = self.line_force.normalized()
        force_factor = math.sqrt(self.line_force.length)
        glVertex2f(*point)
        for i in range(self.SEGMENTS):
            point += direction * segment_len
            glVertex2f(*point)
            d = 1.0 + direction.dot(normal_force)
            angle = direction.get_angle_between(self.line_force)
            deflection = math.log(abs(d) + 0.0001) * angle / rigidity
            direction.rotate(angle * d * force_factor / rigidity)
            rigidity *= rigidity_loss # pole gets more flexible near the end
        glEnd()
        self.end_pt = point
        self.line.set_start_pt(point, direction)
        self.line.draw()


class FishingLine:

    SEGMENTS = 20
    LINE_DAMPING = 0.82
    TROLL_DAMPING = 0.6
    TROLL_SINK_ACCEL = -300
    TROLL_SINK_SPEED = -100 # per second
    TROLL_REEL_SPEED = 150
    BOUNCE_DAMPING = 0.5
    HOOK_DAMPING = 0.99
    TENSION = 0.5
    GRAVITY = -160
    LINE_LENGTH = 0.1
    COLOR = (0.2, 0.25, 0.4)
    WATER_TOP = 0.7

    ## fishing line states
    DANGLING_STATE = 0
    CASTING_STATE = 1
    TROLLING_STATE = 2
    REEL_IN_STATE = 3

    def __init__(self, game, start_pt):
        self.segment_len = game.height * self.LINE_LENGTH / self.SEGMENTS
        self.segment_len2 = self.segment_len**2
        self.pole_end = start_pt
        self.water_level = game.height * self.WATER_TOP
        self.hook = Hook('hook.png')
        #Correct the collision so it corrseponds with the eye of the hook
        self.collision_offset = self.hook.collision_offset
        self.collision_radius = self.hook.collision_radius
        #hook_img = data.image('hook.png')
        # anchor the hook image at the eyelet, which is the rotation point
        #hook_img.anchor_x = hook_img.width / 2
        #hook_img.anchor_y = hook_img.height
        #self.hook_sprite = pyglet.sprite.Sprite(hook_img)
        #self.hook_offset = Vec2d(hook_img.anchor_x, 0)
        #self.hook_speed = 0
        self.sea_floor = game.sea_floor
        self.reset()

    def reset(self):
        """Reset line back to initial state"""
        self.state = self.DANGLING_STATE
        point = Vec2d(*self.pole_end)
        self.last_bounce = None
        self.segments = []
        for i in range(self.SEGMENTS):
            self.segments.append((Vec2d(*point), Vec2d(0,0), Vec2d(0,0)))
            point.y -= self.segment_len

    def switch_hook(self, hook_number):
        """Switch hooks"""
        if hook_number == 1:
            self.hook = Hook('hook.png')
            self.collision_offset = self.hook.collision_offset
            self.collision_radius = self.hook.collision_radius
        if hook_number == 2:
            #print "Dynamite"
            self.hook = Dynamite('hook.png')
            self.collision_offset = self.hook.collision_offset
            self.collision_radius = self.hook.collision_radius

    def set_start_pt(self, start_pt, direction):
        """Set the start point of the line"""
        self.pole_end = start_pt
        self.pole_dir = direction.normalized()
        if self.state == self.DANGLING_STATE:
            old_start, velocity, last_start  = self.segments[0]
            self.segments[0] = (Vec2d(*start_pt), old_start - start_pt, old_start)

    def start_cast(self):
        """Enter the casting state, can only be entered from the dangling state"""
        if self.state == self.DANGLING_STATE:
            self.state = self.CASTING_STATE

    def start_troll(self, entry_position):
        """Start trolling from the given water entry position. 
        can only be entered from the casting state
        """
        if self.state == self.CASTING_STATE:
            entry_position.y = self.water_level
            self.state = self.TROLLING_STATE
            self.hook.hook_speed = self.TROLL_SINK_SPEED * 0.3
            self.water_entry_pt = Vec2d(entry_position.x, self.water_level)
            self.hook.hook_sprite.set_position(*self.water_entry_pt)
            data.sound(random.choice(['plop1.wav', 'plop2.wav', 'plop3.wav'])).play()
            particle.splash_effect.splash(entry_position, particles=8, velocity=150, 
                spread=15, size=30)

    def reel_in(self):
        """Reel the line all the way in"""
        if self.state != self.DANGLING_STATE:
            if self.state == self.TROLLING_STATE:
                # Hook under water, extend the segments to the hook
                to_hook =  Vec2d(self.hook.hook_sprite.x, self.hook.hook_sprite.y) - self.water_entry_pt
                num_segments = int(to_hook.get_length() / self.segment_len)
                if num_segments:
                    segment_vec = to_hook / num_segments
                    position = self.water_entry_pt + segment_vec
                    for i in range(num_segments):
                        self.segments.append((Vec2d(*position), Vec2d(0,0), Vec2d(0,0)))
                        position += segment_vec
            self.state = self.REEL_IN_STATE

    def crank(self):
        """Crank in the line a bit while trolling"""
        if self.state == self.TROLLING_STATE:
            self.hook.hook_speed -= self.TROLL_SINK_SPEED * 1.5

    def update(self, dt):
        if self.state == self.DANGLING_STATE:
            self.update_segments(dt)
        elif self.state == self.CASTING_STATE:
            self.update_cast(dt)
        elif self.state == self.TROLLING_STATE:
            self.update_troll(dt)
        elif self.state == self.REEL_IN_STATE:
            self.update_reel_in(dt)
        else:
            print 'Invalid line state: %s' % self.state

    def update_segments(self, dt, damping=0.75, reverse=False):
        """Update position and velocities of line segments"""
        if reverse:
            segments = reversed(self.segments)
        else:
            segments = iter(self.segments)
        # first segment is the anchor, we do not update it here
        last_seg_position, velocity, last_position = segments.next()

        for position, velocity, last_position in segments:
            last_position.x = position.x
            last_position.y = position.y
            velocity *= damping
            velocity.y += self.GRAVITY * dt
            velocity += (position - last_seg_position) * self.TENSION
            position += velocity
            to_last = last_seg_position - position
            seg_len2 = to_last.get_length_sqrd()
            if seg_len2 > self.segment_len2:
                position += to_last.normalized() * (
                    math.sqrt(seg_len2) - math.sqrt(self.segment_len2))
            #elif seg_len2 < self.segment_len2:
            #    position -= to_last.normalized() * (
            #        math.sqrt(seg_len2) - math.sqrt(self.segment_len2))
            velocity.x = position.x - last_position.x
            velocity.y = position.y - last_position.y
            last_seg_position = position

        # Update the hook position and rotation
        position, nil, nil = self.segments[-2]
        last_seg_position, nil, nil = self.segments[-1]
        self.hook.hook_sprite.rotation = 90 - (position - last_seg_position).get_angle()
        self.hook.hook_sprite.set_position(*last_seg_position)

    def update_cast(self, dt):
        """Update casting state of line"""
        # Update position/velocity of "hook" end
        end_position, velocity, last_position = self.segments[-1]
        velocity *= self.HOOK_DAMPING
        velocity.y += self.GRAVITY * dt
        end_position += velocity
        if end_position.y <= self.water_level:
            self.start_troll(end_position)
            return
        self.update_segments(dt, damping=0.25, reverse=True)

        # Add new segments coming from the pole end
        slack =  self.segments[0][0] - self.pole_end
        velocity = self.segments[0][1]
        new_segments = int(slack.get_length() / self.segment_len)
        if new_segments:
            segment_vec = slack / new_segments
            position = Vec2d(*self.pole_end)
            velocity = Vec2d(*self.pole_dir * velocity.get_length())
            for i in range(new_segments):
                self.segments.insert(0, (Vec2d(*position), Vec2d(*velocity), Vec2d(0, 0)))
                position += segment_vec

    def update_troll(self, dt):
        """Update trolling state of line"""
        self.segments[0] = (Vec2d(*self.pole_end), Vec2d(0,0), Vec2d(0,0))
        self.segments[-1] = (Vec2d(*self.water_entry_pt), Vec2d(0,0), Vec2d(0,0))
        to_water = self.water_entry_pt - self.pole_end
        line_length = to_water.get_length()
        line_pos = Vec2d(*self.pole_end)
        offset = to_water / len(self.segments)

        segments = iter(self.segments[:-1])
        last_seg_position, velocity, last_position = segments.next()
        for position, velocity, last_position in segments:
            last_position.x = position.x
            last_position.y = position.y
            velocity += line_pos - position
            velocity *= self.TROLL_DAMPING
            position += velocity
            to_last = last_seg_position - position
            seg_len2 = to_last.get_length_sqrd()
            if seg_len2 > self.segment_len2:
                position += to_last.normalized() * (
                    math.sqrt(seg_len2) - math.sqrt(self.segment_len2))
            velocity.x = position.x - last_position.x
            velocity.y = position.y - last_position.y
            last_seg_position = position
            line_pos += offset

        self.hook.hook_speed += self.TROLL_SINK_ACCEL * dt
        self.hook.hook_speed = min(max(self.hook.hook_speed, self.TROLL_SINK_SPEED), self.TROLL_REEL_SPEED)
        if self.hook.hook_speed < 0:
            collide_pt = self.sea_floor.collision_point(
                self.hook.hook_sprite, y_offset=-self.hook.hook_sprite.height)
            if collide_pt is not None:
                # hit seafloor
                self.hook.hook_speed = 0
        self.hook.hook_sprite.y += self.hook.hook_speed * dt
        if self.hook.hook_sprite.y > self.water_level:
            self.state = self.REEL_IN_STATE
            return
        if abs(self.hook.hook_sprite.rotation) > 1.0:
            self.hook.hook_sprite.rotation -= self.hook.hook_sprite.rotation * 10.0 * dt

    def update_reel_in(self, dt):
        """Quickly return to dangling state by shortening the line"""
        num_segments = len(self.segments)
        if num_segments > self.SEGMENTS:
            segments_to_pop = max(num_segments / 8, 1)
            if num_segments - segments_to_pop < self.SEGMENTS:
                segments_to_pop = 1
            self.segments = self.segments[segments_to_pop:]
            self.segments[0] = (Vec2d(*self.pole_end), Vec2d(0,0), Vec2d(0,0))
        else:
            # Reeled in all the way
            self.state = self.DANGLING_STATE
        self.update_segments(dt)

    @property
    def collision_position(self):
        """Our collision position is really the hook position"""
        return Vec2d(self.hook.hook_sprite.x + self.hook.collision_offset.x,
            self.hook.hook_sprite.y + self.hook.collision_offset.y) 

    def on_collide(self, system, other):
        """Something collided with us"""
        if isinstance(other, debris.DebrisSprite):
            nil, hook_velocity, nil = self.segments[-1]
            if (self.state == self.CASTING_STATE and hook_velocity.y < 0) or (self.state == self.TROLLING_STATE):
                if self.last_bounce is not other:
                    # bounce off the debris
                    self.last_bounce = other
                    hook_velocity.x *= self.BOUNCE_DAMPING
                    hook_velocity.y = -hook_velocity.y * self.BOUNCE_DAMPING
                else:
                    # hit it twice, reel in
                    self.state = self.REEL_IN_STATE
                    data.sound('honk.mp3').play()

    def draw(self):
        glLineWidth(1)
        glPointSize(5)
        glColor3f(*self.COLOR)
        glBegin(GL_LINE_STRIP)
        #glBegin(GL_POINTS)
        for position, velocity, last in self.segments:
            glVertex2f(*position)
        if self.state == self.TROLLING_STATE:
            glVertex2f(self.hook.hook_sprite.x, self.hook.hook_sprite.y)
        glEnd()
        self.hook.hook_sprite.draw()
